/**
 * Read NS2 trace file line by line.
 */
package util;

import gui.Console;

import ids.Sensor;
import ids.SensorCharacteristics;
import ids.SensorType;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
import java.util.Vector;

import ns2.Event_Type;
import ns2.Node;
import ns2.NodeCharacteristics;
import ns2.Topology;
import ns2.Trace;

public class FileReader {

	private static String LINK = "$ns duplex-link ";

	private static String PROFILE = "##! ";

	private Scanner traceScanner;

	private Scanner topologyScanner;

	private Scanner sensorScanner;

	private Traces trafficTraces;

	private Topology topology;

	private Vector<Sensor> sensors;

	private Console console;

	private Vector<String> profiles;

	public FileReader(File tracefile, File topologyfile, File sensorfile)
			throws SSIDException {
		this.topology = new Topology();
		this.trafficTraces = new Traces();
		this.sensors = new Vector<Sensor>();
		this.console = new Console();
		this.profiles = new Vector<String>();
		try {
			this.traceScanner = new Scanner(tracefile);
		} catch (FileNotFoundException e) {
			throw new SSIDException("ERROR: Trace file is missing.");
		}
		try {
			this.topologyScanner = new Scanner(topologyfile);
		} catch (FileNotFoundException e) {
			throw new SSIDException("ERROR: Topology file is missing.");
		}
		try {
			this.sensorScanner = new Scanner(sensorfile);
		} catch (FileNotFoundException e) {
			throw new SSIDException("ERROR: Sensor settings file is missing.");
		}
	}

	public void readTraceFile() throws SSIDException {
		this.console.println("Start to process NS2 trace file:");
		int counter = 0;
		try {
			while (traceScanner.hasNextLine()) {
				processLine(traceScanner.nextLine());
				counter++;
				if (counter % 5000 == 0)
					this.console
							.println("At network traffic record " + counter);
			}
		} finally {
			traceScanner.close();
		}
		this.console.println("Summary: " + counter
				+ " network traffic records processed in total.");
		this.trafficTraces.trimToSize();
	}

	private void processLine(String aLine) throws SSIDException {
		if (aLine.startsWith("r")) {
			// only process lines start with r (event of receive), to improve
			// running performance
			Trace trace = new Trace(aLine);
			if (trace.event == Event_Type.Receive) {
				// always true as we only process lines start with r
				switch (trace.pkt_type) {
				case rtProtoDV:
					// ignore routing packets
					break;
				case TCP:
					this.trafficTraces.addTrace(trace);
					break;
				case CBR:
					this.trafficTraces.addTrace(trace);
					break;
				case ACK:
					// ignore ACK packets
					break;
				default:
					break;
				}
			}
		}
	}

	public void readTopologyFile() throws SSIDException {
		// Read and analyse topology information in the given tcl file
		while (this.topologyScanner.hasNextLine()) {
			String aLine = this.topologyScanner.nextLine().trim();
			if (aLine.startsWith(LINK)) {
				String src = aLine.split(" ")[2];
				String dst = aLine.split(" ")[3];
				int srcid = new Integer(src.substring(src.indexOf("(") + 1, src
						.indexOf(")")));
				int dstid = new Integer(dst.substring(dst.indexOf("(") + 1, dst
						.indexOf(")")));

				this.topology.nodes.addNode(new Node(srcid));
				this.topology.nodes.addNode(new Node(dstid));

				Node fm_node = this.topology.nodes.getNode(srcid);
				Node to_node = this.topology.nodes.getNode(dstid);

				fm_node.neighbours.addNode(to_node);
				to_node.neighbours.addNode(fm_node);
			} else if (aLine.startsWith(PROFILE)) {
				this.profiles.addElement(aLine);
			}
		}
		// Mark routers from hosts
		for (int i = 0; i < this.topology.nodes.size(); i++) {
			if ((this.topology.nodes.nodeAt(i).id != 0)) {
				this.topology.nodes.nodeAt(i).isRouter = true;
			}
			this.topology.nodes.nodeAt(i).neighbours.trimToSize();
		}

		this.topology.nodes.compact();

		if (this.profiles != null) {
			this.profiles.trimToSize();
			this.profileHandler();
		}
	}

	public void readSensorSettings() throws SSIDException {
		// read and analyse sensor settings in the given file
		String typeStr;
		String sensoridStr;
		String valueStr;
		while (this.sensorScanner.hasNextLine()) {
			String aLine = this.sensorScanner.nextLine().trim();
			if (aLine.startsWith(PROFILE)) {
				typeStr = aLine.split(" ")[1];
				sensoridStr = aLine.split(" ")[2];
				valueStr = aLine.split(" ")[3];

				if (typeStr.equals("$sensortype")) {
					Sensor newsensor = Sensor.createSensor(SensorType
							.parse(valueStr));
					newsensor.id = new Integer(sensoridStr.substring(
							sensoridStr.indexOf("(") + 1, sensoridStr
									.indexOf(")")));
					this.sensors.addElement(newsensor);
				} else {
					float value = Float.parseFloat(valueStr);
					int sensorid = new Integer(sensoridStr.substring(
							sensoridStr.indexOf("(") + 1, sensoridStr
									.indexOf(")")));
					switch (SensorCharacteristics.getCharacteristic(typeStr)) {
					case EFFICIENCY:
						this.getSensor(sensorid).setEfficiency(value);
						break;
					case DEPLOYMENTCOST:
						this.getSensor(sensorid).setDeploymentCost(value);
						break;
					case MONITORINGCOST:
						this.getSensor(sensorid).setMonitoringCost(value);
						break;
					default:
						break;
					}
				}
			}

			if (aLine.startsWith("#!")) {
				typeStr = aLine.split(" ")[1];
				valueStr = aLine.split(" ")[2];
				if (typeStr.equals("$number")) {
					int number = new Integer(valueStr);
					for (int i = 0; i < number; i++) {
						Sensor newsensor = Sensor
								.createSensor(SensorType.ProbeSensor);
						newsensor.id = i;
						this.sensors.addElement(newsensor);
					}
				}
				if (typeStr.equals("$efficiency")) {
					float efficiency = Float.parseFloat(valueStr);
					for (int i = 0; i < this.sensors.size(); i++) {
						this.sensors.elementAt(i).setEfficiency(efficiency);
					}
				}
				if (typeStr.equals("$deploymentcost")) {
					float deploymentCost = Float.parseFloat(valueStr);
					for (int i = 0; i < this.sensors.size(); i++) {
						this.sensors.elementAt(i).setDeploymentCost(
								deploymentCost);
					}
				}
				if (typeStr.equals("$monitoringcost")) {
					float monitoringCost = Float.parseFloat(valueStr);
					for (int i = 0; i < this.sensors.size(); i++) {
						this.sensors.elementAt(i).setMonitoringCost(
								monitoringCost);
					}
				}
			}
		}
		this.sensors.trimToSize();
	}

	private Sensor getSensor(int id) {
		for (int i = 0; i < this.sensors.size(); i++) {
			if (this.sensors.elementAt(i).id == id)
				return this.sensors.elementAt(i);
		}
		return null;
	}

	private void profileHandler() throws SSIDException {
		String aLine;
		String typeStr;
		String nodeidStr;
		String valueStr;
		for (int i = 0; i < this.profiles.size(); i++) {
			aLine = this.profiles.elementAt(i);
			typeStr = aLine.split(" ")[1];
			nodeidStr = aLine.split(" ")[2];
			valueStr = aLine.split(" ")[3];
			int nodeid = new Integer(nodeidStr.substring(
					nodeidStr.indexOf("(") + 1, nodeidStr.indexOf(")")));
			float value = Float.parseFloat(valueStr);
			switch (NodeCharacteristics.getCharacteristic(typeStr)) {
			case RISKPROFILE:
				this.topology.nodes.getNode(nodeid).setRiskProfile(value);
				break;
			case LOADFACTOR:
				this.topology.nodes.getNode(nodeid).setLoadFactor(value);
				break;
			case DISRUPTIONCOST:
				this.topology.nodes.getNode(nodeid).setDisruptionCost(value);
				break;
			default:
				break;
			}
		}
	}

	public Traces getTrafficTraces() {
		return this.trafficTraces;
	}

	public Topology getTopology() {
		return this.topology;
	}

	public Vector<Sensor> getSensors() {
		return this.sensors;
	}

	public void setConsole(Console console) {
		this.console = console;
	}

	public void process() {
		this.topology.update(this.trafficTraces);
	}
}
